package com.hiabr.iep.gateway.filter;

import com.alibaba.fastjson.JSON;
import com.hiabr.iep.common.domain.constant.CommonConstant;
import com.hiabr.iep.common.domain.constant.RedisConstant;
import com.hiabr.iep.common.domain.vo.req.BaseAdminReq;
import com.hiabr.iep.common.domain.vo.req.BaseUserReq;
import com.hiabr.iep.common.domain.vo.resp.BaseResponse;
import com.hiabr.iep.common.domain.vo.resp.ErrorType;
import com.hiabr.iep.common.redis.service.RedisService;
import com.hiabr.iep.common.utils.SignUtil;
import com.hiabr.iep.gateway.params.ParamsUtil;
import com.hiabr.iep.gateway.properties.CustomGatewayProperties;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.PathMatcher;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Stream;

/**
 * <p>
 * AuthFilter （依赖LogFilter的参数解析）
 * </p>
 *
 * @author jie huang
 */
@Component
@Slf4j
public class AuthFilter implements GlobalFilter, Ordered {

    private static final PathMatcher pathMatcher = new AntPathMatcher(System.getProperty("file.separator"));

    @Autowired
    private CustomGatewayProperties gatewayProperties;
    @Autowired
    private RedisService redisService;

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        Map<String, Object> params = ParamsUtil.getAllParams(exchange);
        //访问ip
        String ip = Objects.requireNonNull(request.getRemoteAddress()).getAddress().getHostAddress();
        String rawPath = request.getURI().getRawPath();
        String appCode = request.getHeaders().getFirst(CommonConstant.APPCODE);
        String traceId = request.getHeaders().getFirst(CommonConstant.TRACE_ID);
        String ts = request.getHeaders().getFirst(CommonConstant.TS);
        String sign = request.getHeaders().getFirst(CommonConstant.SIGN);
        //token
        String token = request.getHeaders().getFirst(CommonConstant.TOKEN);
        //验证签名
        if (gatewayProperties.getSign().isEnable() && !ignoreSignPath(request.getURI().getPath())) {
            //签名参数不完整
            if (StringUtils.isEmpty(appCode) || StringUtils.isEmpty(traceId) || StringUtils.isEmpty(ts) || StringUtils.isEmpty(sign)) {
                log.error("签名参数为空: ip:{}, url:{}, appCode:{}, ts:{}, sign:{}", ip, rawPath, appCode, ts, sign);
                return responseError(exchange, BaseResponse.error(ErrorType.ERROR_SIGNATURE_PARAM));
            }
            //时间差
            if (Math.abs(System.currentTimeMillis() - Long.parseLong(ts)) > gatewayProperties.getSign().getTimeout()) {
                log.error("签名时间戳错误: ip:{}, url:{}, appCode:{}, ts:{}, sign:{}", ip, rawPath, appCode, ts, sign);
                return responseError(exchange, BaseResponse.error(ErrorType.ERROR_SIGNATURE_TS));
            }
            params.put(CommonConstant.APPCODE, appCode);
            params.put(CommonConstant.TOKEN, token);
            params.put(CommonConstant.TRACE_ID, traceId);
            params.put(CommonConstant.TS, ts);
            //校验sign
            String ourSign = SignUtil.buildSign(params, gatewayProperties.getSign().getKey().get(appCode));
            if (!sign.equals(ourSign)) {
                log.error("签名错误: ip:{}, url:{}, appCode:{}, ts:{}, sign:{}, ourSign:{}", ip, rawPath, appCode, ts, sign, ourSign);
                return responseError(exchange, BaseResponse.error(ErrorType.ERROR_SIGNATURE));
            }
        }
        ServerHttpRequest newRequest = exchange.getRequest();
        BaseUserReq baseUserReq = null;
        BaseAdminReq baseAdminReq = null;
        String userHeader = null;
        String adminHeader = null;
        //验证权限
        if (gatewayProperties.getAuth().isEnable() && !ignoreAuthPath(request.getURI().getPath())) {
            //无效token
            if (StringUtils.isEmpty(token)) {
                log.error("token为空: ip:{}, url:{}, appCode:{}, traceId:{}, ts:{}", ip, rawPath, appCode, traceId, ts);
                return responseError(exchange, BaseResponse.error(ErrorType.ERROR_TOKEN));
            }
            switch (appCode) {
                case CommonConstant.APP_CODE_ADMIN:
                    baseAdminReq = getAdmin(token);
                    break;
                case CommonConstant.APP_CODE_PLATFORM:
                    baseUserReq = getUser(token);
                    break;
                default:
                    log.error("无效的appid: ip:{}, url:{}, appCode:{}", ip, rawPath, appCode);
                    return responseError(exchange, BaseResponse.error(ErrorType.ERROR_APPCODE));
            }
            if (Objects.isNull(baseAdminReq) && Objects.isNull(baseUserReq)) {
                return responseError(exchange, BaseResponse.error(ErrorType.ERROR_TOKEN));
            }
            // TODO 权限验证
        }
        try {
            if (baseUserReq != null) {
                userHeader = URLEncoder.encode(JSON.toJSONString(baseUserReq), "UTF-8");
            }
            if (baseAdminReq != null) {
                adminHeader = URLEncoder.encode(JSON.toJSONString(baseAdminReq), "UTF-8");
            }
        } catch (UnsupportedEncodingException e) {
            log.error("URLDecoder.encode failed, userHeader:{}, adminHeader:{}", userHeader, adminHeader, e);
        }
        newRequest.mutate()
                .header(CommonConstant.USER, userHeader)
                .header(CommonConstant.ADMIN, adminHeader)
                .build();
        return chain.filter(exchange.mutate().request(newRequest).build());

    }

    /**
     * 判断此url是否验证签名
     *
     * @param url
     * @return
     */
    private boolean ignoreSignPath(String url) {
        return Stream.of(gatewayProperties.getSign().getIgnoreUrls()).anyMatch(ignoreUrl -> pathMatcher.match(ignoreUrl, url));
    }

    /**
     * 判断此url是否验证权限
     *
     * @param url
     * @return
     */
    private boolean ignoreAuthPath(String url) {
        return Stream.of(gatewayProperties.getAuth().getIgnoreUrls()).anyMatch(ignoreUrl -> pathMatcher.match(ignoreUrl, url));
    }

    private BaseAdminReq getAdmin(String token) {
        Object admin = redisService.get(RedisConstant.TOKEN_ADMIN + token);
        return admin == null ? null : (BaseAdminReq) admin;
    }

    private BaseUserReq getUser(String token) {
        Object user = redisService.get(RedisConstant.TOKEN_USER + token);
        return user == null ? null : (BaseUserReq) user;
    }

    /**
     * 自定义返回
     *
     * @param exchange
     * @param baseResponse
     * @return
     */
    private Mono<Void> responseError(ServerWebExchange exchange, BaseResponse baseResponse) {
        ServerHttpResponse serverHttpResponse = exchange.getResponse();
        serverHttpResponse.getHeaders().setContentType(MediaType.APPLICATION_JSON);
        byte[] bytes = JSON.toJSONString(baseResponse).getBytes(StandardCharsets.UTF_8);
        DataBuffer buffer = serverHttpResponse.bufferFactory().wrap(bytes);
        return serverHttpResponse.writeWith(Flux.just(buffer));
    }

    @Override
    public int getOrder() {
        return Ordered.HIGHEST_PRECEDENCE + 2;
    }

}
